﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using AnaxibiaStudios.BeanPermutator;
using AnaxibiaStudios.BeanPermutator.Gray;

namespace TestBeanPermutator
{
	[TestClass]
	public class PermutationsTest
	{
		[TestMethod]
		public void LevelOneTest()
		{
			SmartSourceTester(1);
		}

		[TestMethod]
		public void LevelTwoTest()
		{
			SmartSourceTester(2);
		}

		[TestMethod]
		public void LevelThreeTest()
		{
			SmartSourceTester(3);
		}

		[TestMethod]
		public void LevelFourTest()
		{
			SmartSourceTester(4);
		}

		[TestMethod]
		public void LevelFiveTest()
		{
			SmartSourceTester(5);
		}


		[TestMethod]
		public void FullPermsTest()
		{
			for (int prime = 1; prime <= 10; prime++)
			{
				SmartSourceTester(prime);
			}
		}

		private void SmartSourceTester(int count)
		{
			SmartSourceTester(string.Join(string.Empty, Enumerable.Range(1, count)));
		}

		private void SmartSourceTester(string source)
		{
			foreach (var rule in BuildAllRules(source))
			{
				System.Diagnostics.Debug.WriteLine(rule);
				AssertAllPerms(source, rule.Swap, rule.ToString());
			}
		}

		class SingleRule
		{
			public char A { get; protected set; }
			public char B { get; protected set; }

			public SingleRule(char ea, char eb)
			{
				A = ea;
				B = eb;
			}

			public bool Swap(char ea, char eb)
			{
				return !(A == ea && B == eb);
			}

			public override string ToString()
			{
				return string.Format("Rule({0}<{1})", A, B);
			}
		}

		class SwapRuleWrapper
		{
			public IEnumerable<SingleRule> Rules { get; protected set; }

			public SwapRuleWrapper(IEnumerable<SingleRule> rules)
			{
				Rules = rules.ToArray();
			}

			public bool Swap(char a, char b)
			{
				// collect all failures as TRUE
				// return first failure (which is TRUE) -- else false (no failure)
				// negate result (TRUE means failure found, but return false)

				return !Rules.Select(r => !r.Swap(a, b)).FirstOrDefault(s => s);
			}

			public override string ToString()
			{
				return string.Join(" & ", Rules);
			}
		}

		private IEnumerable<IEnumerable<T>> Subsets<T>(IEnumerable<T> set)
		{
			if (set.Count() == 0)
			{
				yield return new T[0];
				yield break;
			}

			foreach (var sub in Subsets<T>(set.Skip(1)))
			{
				var next = new List<T>();
				next.AddRange(sub);
				yield return next;

				next.Add(set.First());
				yield return next;
			}
		}

		private IEnumerable<string> Subsets(string source)
		{
			IEnumerable<char> chars = source.ToArray();

			foreach (var gsub in Subsets<char>(chars))
			{
				yield return string.Join(string.Empty, gsub);
			}
		}

		private IEnumerable<string> Subsets(string source, int count)
		{
			if (count > source.Length || count == 0)
			{
				yield return string.Empty;
				yield break;
			}

			for (int i = 0; i < source.Length; i++)
			{
				string next = source.Substring(i, 1);

				if (source.Length == i + 1)
				{
					yield return next;
					break;
				}

				foreach (var sub in Subsets(source.Substring(i + 1), count - 1))
				{
					yield return next + sub;
				}
			}
		}

		private IEnumerable<SingleRule> AllRules(string source)
		{
			for (int backIndex = 0; backIndex < source.Length - 1; backIndex++)
			{
				char alpha = source[backIndex];

				for (int nextIndex = backIndex + 1; nextIndex < source.Length; nextIndex++)
				{
					char bravo = source[nextIndex];

					yield return new SingleRule(alpha, bravo);
				}
			}
		}

		private IEnumerable<SwapRuleWrapper> BuildAllRules(string source)
		{
			IEnumerable<SingleRule> rules = AllRules(source);

			foreach (var ruleSet in Subsets(rules))
			{
				yield return new SwapRuleWrapper(ruleSet);
			}
		}

		private void AssertAllPerms(string src, Func<char, char, bool> comp)
		{
			AssertAllPerms(src, comp, string.Empty);
		}

		private void AssertAllPerms(string src, Func<char, char, bool> comp, string label)
		{
			comp = comp == null ? (a, b) => true : comp;
			var chars = src.ToList();

			var brute = BruteForce(chars, comp);

			var gp = Permutator.Create<Permutator<char>, char>(chars, comp);
			var gi = Iterator.CreateFrom(gp);

			var gray = ToStrings(gi);

			AssertEquivalentLists(brute, gray, label);
		}

		private void AssertEquivalentLists(List<string> expected, List<string> actual, string label)
		{
			string prefix = string.IsNullOrEmpty(label) ? string.Empty : string.Format("Test [{0}]. ", label);

			if (expected.Count > actual.Count)
			{
				Assert.Fail(prefix + "Permutations missing: {0}", string.Join(", ", expected.Where(e => !actual.Contains(e)).ToArray()));
			}
			else if (expected.Count < actual.Count)
			{
				Assert.Fail(prefix + "Permutations extra: {0}", string.Join(", ", actual.Where(a => !expected.Contains(a)).ToArray()));
			}

			string missing = "";
			string repeated = "";

			for (int i = 0; i < actual.Count; i++)
			{
				var act = actual[i];
				if (actual.GetRange(i + 1, actual.Count - i - 1).Contains(act))
				{
					repeated += act.ToString() + ",";
				}
			}

			foreach (var exp in expected)
			{
				if (!actual.Contains(exp))
				{
					missing += exp.ToString() + ",";
				}
			}

			if (!string.IsNullOrEmpty(missing))
			{
				Assert.Fail(prefix + "Missing [{0}], Repeated [{1}]", missing, repeated);
			}
		}

		private List<string> BruteForce(IList<char> src, Func<char, char, bool> canSwap)
		{
			var gp = Permutator.Create<AnaxibiaStudios.BeanPermutator.Brute.Permutator<char>, char>(src, canSwap);
			var gi = Iterator.CreateFrom(gp);
			var all = ToStrings(gi);
			return all;
		}

		private List<string> ToStrings<T>(IEnumerable<IEnumerable<T>> list)
		{
			return list.Select(t => string.Join("", t.ToArray())).ToList();
		}
	}
}
